package com.mogujie.jessica.util;

import java.util.Comparator;


/**
 * Represents byte[], as a slice (offset + length) into an existing byte[]. The
 * {@link #bytes} member should never be null; use {@link #EMPTY_BYTES} if
 * necessary.
 * 
 * @lucene.experimental
 */
public final class BytesRef implements Comparable<BytesRef>
{

    static final int HASH_PRIME = 31;
    public static final byte[] EMPTY_BYTES = new byte[0];

    /** The contents of the BytesRef. Should never be {@code null}. */
    public byte[] bytes;

    /** Offset of first valid byte. */
    public int offset;

    /** Length of used bytes. */
    public int length;

    public BytesRef()
    {
        bytes = EMPTY_BYTES;
    }

    /**
     * This instance will directly reference bytes w/o making a copy. bytes
     * should not be null.
     */
    public BytesRef(byte[] bytes, int offset, int length)
    {
        assert bytes != null;
        this.bytes = bytes;
        this.offset = offset;
        this.length = length;
    }

    /**
     * This instance will directly reference bytes w/o making a copy. bytes
     * should not be null
     */
    public BytesRef(byte[] bytes)
    {
        assert bytes != null;
        this.bytes = bytes;
        this.offset = 0;
        this.length = bytes.length;
    }

    public BytesRef(int capacity)
    {
        this.bytes = new byte[capacity];
    }

    /**
     * @param text
     *            Initialize the byte[] from the UTF8 bytes for the provided
     *            Sring. This must be well-formed unicode text, with no unpaired
     *            surrogates or U+FFFF.
     */
    public BytesRef(CharSequence text)
    {
        this();
        copy(text);
    }

    /**
     * @param text
     *            Initialize the byte[] from the UTF8 bytes for the provided
     *            array. This must be well-formed unicode text, with no unpaired
     *            surrogates or U+FFFF.
     */
    public BytesRef(char text[], int offset, int length)
    {
        this(length * 4);
        copy(text, offset, length);
    }

    public BytesRef(BytesRef other)
    {
        this();
        copy(other);
    }

    /*
     * // maybe? public BytesRef(BytesRef other, boolean shallow) { this(); if
     * (shallow) { offset = other.offset; length = other.length; bytes =
     * other.bytes; } else { copy(other); } }
     */

    /**
     * Copies the UTF8 bytes for this string.
     * 
     * @param text
     *            Must be well-formed unicode text, with no unpaired surrogates
     *            or invalid UTF16 code units.
     */
    public void copy(CharSequence text)
    {
        UnicodeUtil.UTF16toUTF8(text, 0, text.length(), this);
    }

    /**
     * Copies the UTF8 bytes for this string.
     * 
     * @param text
     *            Must be well-formed unicode text, with no unpaired surrogates
     *            or invalid UTF16 code units.
     */
    public void copy(char text[], int offset, int length)
    {
        UnicodeUtil.UTF16toUTF8(text, offset, length, this);
    }

    public boolean bytesEquals(BytesRef other)
    {
        if (length == other.length)
        {
            int otherUpto = other.offset;
            final byte[] otherBytes = other.bytes;
            final int end = offset + length;
            for (int upto = offset; upto < end; upto++, otherUpto++)
            {
                if (bytes[upto] != otherBytes[otherUpto])
                {
                    return false;
                }
            }
            return true;
        } else
        {
            return false;
        }
    }

    @Override
    public Object clone()
    {
        return new BytesRef(this);
    }

    private boolean sliceEquals(BytesRef other, int pos)
    {
        if (pos < 0 || length - pos < other.length)
        {
            return false;
        }
        int i = offset + pos;
        int j = other.offset;
        final int k = other.offset + other.length;

        while (j < k)
        {
            if (bytes[i++] != other.bytes[j++])
            {
                return false;
            }
        }

        return true;
    }

    public boolean startsWith(BytesRef other)
    {
        return sliceEquals(other, 0);
    }

    public boolean endsWith(BytesRef other)
    {
        return sliceEquals(other, length - other.length);
    }

    /**
     * Calculates the hash code as required by TermsHash during indexing.
     * <p>
     * It is defined as:
     * 
     * <pre>
     * int hash = 0;
     * for (int i = offset; i &lt; offset + length; i++)
     * {
     *     hash = 31 * hash + bytes[i];
     * }
     * </pre>
     */
    @Override
    public int hashCode()
    {
        int result = 0;
        final int end = offset + length;
        for (int i = offset; i < end; i++)
        {
            result = HASH_PRIME * result + bytes[i];
        }
        return result;
    }

    @Override
    public boolean equals(Object other)
    {
        if (other == null)
        {
            return false;
        }
        return this.bytesEquals((BytesRef) other);
    }

    /**
     * Interprets stored bytes as UTF8 bytes, returning the resulting string
     */
    public String utf8ToString()
    {
        final CharsRef ref = new CharsRef(length);
        UnicodeUtil.UTF8toUTF16(bytes, offset, length, ref);
        return ref.toString();
    }

    /** Interprets stored bytes as UTF8 bytes into the given {@link CharsRef} */
    public CharsRef utf8ToChars(CharsRef ref)
    {
        UnicodeUtil.UTF8toUTF16(bytes, offset, length, ref);
        return ref;
    }

    /** Returns hex encoded bytes, eg [0x6c 0x75 0x63 0x65 0x6e 0x65] */
    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        final int end = offset + length;
        for (int i = offset; i < end; i++)
        {
            if (i > offset)
            {
                sb.append(' ');
            }
            sb.append(Integer.toHexString(bytes[i] & 0xff));
        }
        sb.append(']');
        return sb.toString();
    }

    public void copy(BytesRef other)
    {
        if (bytes.length < other.length)
        {
            bytes = new byte[other.length];
        }
        System.arraycopy(other.bytes, other.offset, bytes, 0, other.length);
        length = other.length;
        offset = 0;
    }

    public void append(BytesRef other)
    {
        int newLen = length + other.length;
        if (bytes.length < newLen)
        {
            byte[] newBytes = new byte[newLen];
            System.arraycopy(bytes, offset, newBytes, 0, length);
            offset = 0;
            bytes = newBytes;
        }
        System.arraycopy(other.bytes, other.offset, bytes, length + offset, other.length);
        length = newLen;
    }

    public void grow(int newLength)
    {
        bytes = ArrayUtil.grow(bytes, newLength);
    }

    /** Unsigned byte order comparison */
    public int compareTo(BytesRef other)
    {
        if (this == other)
            return 0;

        final byte[] aBytes = this.bytes;
        int aUpto = this.offset;
        final byte[] bBytes = other.bytes;
        int bUpto = other.offset;

        final int aStop = aUpto + Math.min(this.length, other.length);

        while (aUpto < aStop)
        {
            int aByte = aBytes[aUpto++] & 0xff;
            int bByte = bBytes[bUpto++] & 0xff;
            int diff = aByte - bByte;
            if (diff != 0)
                return diff;
        }

        // One is a prefix of the other, or, they are equal:
        return this.length - other.length;
    }

    private final static Comparator<BytesRef> utf8SortedAsUnicodeSortOrder = new UTF8SortedAsUnicodeComparator();

    public static Comparator<BytesRef> getUTF8SortedAsUnicodeComparator()
    {
        return utf8SortedAsUnicodeSortOrder;
    }

    private static class UTF8SortedAsUnicodeComparator implements Comparator<BytesRef>
    {
        // Only singleton
        private UTF8SortedAsUnicodeComparator()
        {
        };

        public int compare(BytesRef a, BytesRef b)
        {
            final byte[] aBytes = a.bytes;
            int aUpto = a.offset;
            final byte[] bBytes = b.bytes;
            int bUpto = b.offset;

            final int aStop;
            if (a.length < b.length)
            {
                aStop = aUpto + a.length;
            } else
            {
                aStop = aUpto + b.length;
            }

            while (aUpto < aStop)
            {
                int aByte = aBytes[aUpto++] & 0xff;
                int bByte = bBytes[bUpto++] & 0xff;

                int diff = aByte - bByte;
                if (diff != 0)
                {
                    return diff;
                }
            }

            // One is a prefix of the other, or, they are equal:
            return a.length - b.length;
        }
    }

    private final static Comparator<BytesRef> utf8SortedAsUTF16SortOrder = new UTF8SortedAsUTF16Comparator();

    public static Comparator<BytesRef> getUTF8SortedAsUTF16Comparator()
    {
        return utf8SortedAsUTF16SortOrder;
    }

    private static class UTF8SortedAsUTF16Comparator implements Comparator<BytesRef>
    {
        // Only singleton
        private UTF8SortedAsUTF16Comparator()
        {
        };

        public int compare(BytesRef a, BytesRef b)
        {

            final byte[] aBytes = a.bytes;
            int aUpto = a.offset;
            final byte[] bBytes = b.bytes;
            int bUpto = b.offset;

            final int aStop;
            if (a.length < b.length)
            {
                aStop = aUpto + a.length;
            } else
            {
                aStop = aUpto + b.length;
            }

            while (aUpto < aStop)
            {
                int aByte = aBytes[aUpto++] & 0xff;
                int bByte = bBytes[bUpto++] & 0xff;

                if (aByte != bByte)
                {

                    // See
                    // http://icu-project.org/docs/papers/utf16_code_point_order.html#utf-8-in-utf-16-order

                    // We know the terms are not equal, but, we may
                    // have to carefully fixup the bytes at the
                    // difference to match UTF16's sort order:

                    // NOTE: instead of moving supplementary code points (0xee
                    // and 0xef) to the unused 0xfe and 0xff,
                    // we move them to the unused 0xfc and 0xfd [reserved for
                    // future 6-byte character sequences]
                    // this reserves 0xff for preflex's term reordering
                    // (surrogate dance), and if unicode grows such
                    // that 6-byte sequences are needed we have much bigger
                    // problems anyway.
                    if (aByte >= 0xee && bByte >= 0xee)
                    {
                        if ((aByte & 0xfe) == 0xee)
                        {
                            aByte += 0xe;
                        }
                        if ((bByte & 0xfe) == 0xee)
                        {
                            bByte += 0xe;
                        }
                    }
                    return aByte - bByte;
                }
            }

            // One is a prefix of the other, or, they are equal:
            return a.length - b.length;
        }
    }
}
